package com.zzedu.framework.aspectj;

import com.zzedu.common.annotation.RateLimiter;
import com.zzedu.common.exception.RateLimiterException;
import com.zzedu.common.utils.StringUtils;
import com.zzedu.framework.ratelimit.RedisSlidingWindowRateLimiter;
import com.zzedu.system.service.ISysConfigService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.google.common.collect.Maps;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 限流切面
 */
@Aspect
@Component
@Order(1005)
public class RateLimiterAspect {

    private Logger logger = LoggerFactory.getLogger(this.getClass());


    @Autowired
    RedisSlidingWindowRateLimiter redisSlidingWindowRateLimiter;

    @Autowired
    ISysConfigService sysConfigService;

    Map<String, String> preValueMap = Maps.newConcurrentMap();

    @Pointcut("@annotation(com.zzedu.common.annotation.RateLimiter)")
    public void rateLimit() {
    }

    @Before("rateLimit()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        RateLimiter rateLimiter = AnnotationUtils.findAnnotation(signature.getMethod(), RateLimiter.class);
        if (null != rateLimiter) {
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            String rateKey = rateLimiter.rateKey();
            if (!StringUtils.isNotBlank(rateKey)) {
                rateKey = className + "." + methodName;
            }
            if (StringUtils.isNotBlank(rateKey)) {
                String value = sysConfigService.selectConfigByKeyCache(rateKey);
                if (StringUtils.isNotBlank(value)) {
                    boolean isAddKey = !preValueMap.containsKey(rateKey) || !value.equals(preValueMap.get(rateKey));
                    if (isAddKey) {
                        preValueMap.put(rateKey, value);
                        try {
                            RedisSlidingWindowRateLimiter.RateLimitConfig rateLimitConfig = JSON.parseObject(value, new TypeReference<RedisSlidingWindowRateLimiter.RateLimitConfig>() {{
                            }});
                            redisSlidingWindowRateLimiter.updateRateLimitConfig(rateKey, rateLimitConfig);
                        } catch (Exception e) {
                            logger.error("更新限流配置出错");
                        }
                    }
                    if (!redisSlidingWindowRateLimiter.isAllowed(rateKey)) {
                        logger.info("限流key:{} 被命中,限流说明:{}", rateKey, rateLimiter.rateKeyText());
                        throw new RateLimiterException(rateKey + ":" + rateLimiter.rateKeyText() + "-已达到请并发阀值");
                    }
                } else {
                    preValueMap.remove(rateKey);
                }

            }
        }
    }

}
